<?php

namespace ToolKit\Sign;

use ToolKit\Exceptions\ValidationException;
use ToolKit\Sign\Engine\AlgorithmEngineInterface;

class SignManager
{
    const KEY_SIGN = 'sign';
    const KEY_TIMESTAMP = 'timestamp';
    const KEY_NONCE_STR = 'nonce_str';

    /** @var array $nameData */
    protected $nameData = [
        self::KEY_SIGN => 'sign',
        self::KEY_TIMESTAMP => 'timestamp',
        self::KEY_NONCE_STR => 'nonce_str',
    ];
    /** @var array $excludeKeys */
    protected $excludeKeys = [];

    /** @var AlgorithmEngineInterface $algorithmEngine */
    protected $algorithmEngine;

    public function __construct(AlgorithmEngineInterface $algorithmEngine)
    {
        $this->algorithmEngine = $algorithmEngine;
    }

    /**
     * @param string $key
     * @param string $name
     * @return bool
     * @throws ValidationException
     */
    public function setName(string $key, string $name)
    {
        if (!array_key_exists($key, $this->nameData)) {
            throw new ValidationException('key不存在, key:' . $key);
        }
        $this->nameData[self::KEY_SIGN] = $name;
        return true;
    }

    /**
     * @param $key
     * @return mixed
     */
    protected function getName($key)
    {
        return $this->nameData[$key];
    }

    /**
     * @param array $excludeKeys
     * @return bool
     */
    public function setExcludeKeys(array $excludeKeys)
    {
        $this->excludeKeys = $excludeKeys;
        return true;
    }

    /**
     * @return array
     */
    protected function getFinalExcludeKeys()
    {
        return array_merge($this->excludeKeys, [$this->getName('sign')]);
    }

    /**
     * @param $params
     * @return array
     */
    protected function formatParams($params)
    {
        $excludeKeys = $this->getFinalExcludeKeys();
        $newParams = [];
        foreach ($params as $key => $value) {
            if (!in_array($key, $excludeKeys)) {
                $newParams[$key] = $value;
            }
        }
        return $newParams;
    }

    /**
     * @param array $params
     * @param string $secret
     * @return string
     * @throws ValidationException
     */
    public function generate(array $params, string $secret): string
    {
        $this->validate($params, $secret);
        $params = $this->formatParams($params);
        $this->algorithmEngine->setSecret($secret);
        return $this->algorithmEngine->generate($params);
    }

    /**
     * @param array $params
     * @param string $secret
     * @return bool
     * @throws ValidationException
     */
    public function verify(array $params, string $secret): bool
    {
        $this->validate($params, $secret);
        if (!isset($params[$this->getName(self::KEY_SIGN)]) || empty($params[$this->getName(self::KEY_SIGN)])) {
            throw new ValidationException('签名不能为空');
        }

        $actualSign = $params[$this->getName(self::KEY_SIGN)];
        $params = $this->formatParams($params);
        $this->algorithmEngine->setSecret($secret);
        $sign = $this->algorithmEngine->generate($params);
        if ($sign == $actualSign) {
            return true;
        }
        return false;
    }

    /**
     * @param array $params
     * @param string $secret
     * @return array
     * @throws ValidationException
     */
    public function attach(array $params, string $secret): array
    {
        $this->validate($params, $secret);
        $params = $this->formatParams($params);
        $params = $this->attachParams($params);
        $this->algorithmEngine->setSecret($secret);
        $expectedSign = $this->algorithmEngine->generate($params);
        $params[$this->getName(self::KEY_SIGN)] = $expectedSign;
        return $params;
    }

    /**
     * @param $params
     * @return mixed
     */
    protected function attachParams($params)
    {
        if (!isset($params[$this->getName(self::KEY_TIMESTAMP)]) || empty($params[$this->getName(self::KEY_TIMESTAMP)])) {
            $params[$this->getName(self::KEY_TIMESTAMP)] = time();
        }
        if (!isset($params[$this->getName(self::KEY_NONCE_STR)]) || empty($params[$this->getName(self::KEY_NONCE_STR)])) {
            $params[$this->getName(self::KEY_NONCE_STR)] = $this->getNonceStr();
        }
        return $params;
    }

    /**
     * @param int $length
     * @return string
     */
    protected function getNonceStr($length = 10)
    {
        $chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
        $charLength = strlen($chars);
        $str = '';
        for ($i = 0; $i < $length; $i++) {
            $str .= substr($chars, mt_rand(0, $charLength - 1), 1);
        }
        return $str;
    }

    /**
     * @param $params
     * @param $secret
     * @return bool
     * @throws ValidationException
     */
    protected function validate($params, $secret)
    {
        if (empty($params)) {
            throw new ValidationException('参数不能为空');
        }
        if (empty($secret)) {
            throw new ValidationException('秘钥不能为空');
        }
        return true;
    }

    /**
     * @return array
     */
    public function getDebugInfo()
    {
        return $this->algorithmEngine->getDebugInfo();
    }
}